<?php
/**
 * @copyright Arthur Beer
 * @package core
 *
 * @copyright TinyWEB CMS
 * @link http://tinywebcms.ru
 * @link http://cms.vingrad.ru
 * @version SVN: $Id$ 
 */
 
 /**
  * ApplicationRunner
  */
class ApplicationRunner extends Object 
{
    /**
     * Arguments
     *
     * @var array
     */
    private $args = array();
    
    public function __construct($application = null)
    {
        if($application) {
            $this->listen($application);
        }
    }
    
    public function listen($application = null)
    {
        $this->setNamespace($application);
        $this->run();
    }
    
    private function run()
    {
    	$this->configure();
        $routing = $this->getProperty('routing');
        $route = $routing->routes;
        
        $log = array();
        $log[] = 'run';
        if(empty($this->args)) {
        	if($routing->exists('defaults')) {
	        	$route = $routing->defaults->top;
	        	$invoke = $route->invoke;
	        }
        } else {
            $log[] = 'start lookup at: /' . implode('/', $this->args);
        	$i = 0;
        	$p = 0;
	        foreach ($this->args as $level) {
	        	$i++;
	        	
	        	if(empty($level)) {
	        		continue;
	        	}
	        	/* @var $route ConfigProperty */
	        	if($route->exists($level)) {
	        	    $log[] = 'matching level: ' . $level;
	        		$route = $route->$level;
	        		if(count($this->args) == $i && $route->exists('invoke')) {
	        			$invoke = $route->invoke;
	        		}
	        	} else if($route->exists('rules')) {
	        	    $log[] = 'check rules in level: ' . $route->getName();
	        		foreach ($route->rules as $rule) {
	        			$success = false;
	        			$params = $rule->getAttributes();
	        			if(!isset($params['method'])) {
	        				$params['method'] = 'regex';
	        			}
	        			
	        			if(!isset($params['match'])) {
	        				$params['match'] = '.+';
	        				$params['method'] = 'regex';
	        			}
	        			
	        			if(!isset($params['param'])) {
	        			    $params['param'] = $p++;
	        			}
	        			
	        			switch ($params['method']) {
	        				case 'equal':
	        					if($level == $params['match']) {
	        						$success = true;
    								Env::setParam($params['param'], $params['match']);
	        					}
	        					break;
	        				case 'regex':
        						if(preg_match("/^(?<param>{$params['match']})$/ui", $level, $match)) {
        							$success = true;
    								Env::setParam($params['param'], $match['param']);
        						}
        						break;
	        			}

	        			if($success) {
	        			    $log[] = "check rule: {$params['method']} ({$params['match']}, $level) : success";
	        			    if(isset($params['param']) && $params['param']) {
	        			        $log[] = "store: " . $params['param'] . " = " . (($params['method'] == 'regex') ? (isset($match['param']) ? $match['param'] : 'NULL') : $level); 
	        			    }
	        			    
	        			    $route = $rule;
	        				if($rule->exists('invoke')) {
	        					$invoke = $rule->invoke;
	        				} else if($route->exists('invoke')) {
	        					$invoke = $route->invoke;
	        				}

	        				break;
	        			}
	        			
	        			$log[] = "check rule: {$params['method']} ({$params['match']}, $level) : failed"; 
	        		}

	        		//break;
	        	} else {
	        		break;
	        	}
	        }
        }
        
        $log[] = 'start compilation...';

        try {
        	if(!isset($invoke) || !class_exists($invoke->value)) {
        		throw new HTTP404Exception();
        	}
        	
        	$log[] = 'init: ' . $invoke->value;
        	$Reflection = new ReflectionClass($invoke->value);
        	$Application = $Reflection->newInstance();
        	
        	/* @var $Application ApplicationController */
        	if(!$Application instanceof ApplicationController) {
        		throw new Exception('ApplicationRunner: Called application is not extends of ApplicationController class.');
        	}
        	
        	$Application->prepare();
        	$listener = ApplicationController::getEventListener($Application, 'exec');

        	if($Application instanceof IExecutable && !$listener) {
        	    $log[] = 'call: exec';
        		$Application->exec();
        	}
        	
        	/**
        	 * Check events and call them
        	 */
        	foreach (ApplicationController::getEventListeners() as $listener) {
        	    if($listener->isEventHappened()) {
        	        $listener->call();
    	        }
        	    
        	    $listener->dispatch();
        	}
        	
        	if($Application instanceof IViewable) {
        		if($route->exists('decorator')) {
        			$decorator = $route->decorator->value;
        		} else if($routing->defaults->exists('decorator')) {
        			$decorator = $routing->defaults->decorator->value;
        		}

        		if($decorator) {
        		    $log[] = 'decorate by: ' . $decorator;
	        		$Reflection = new ReflectionClass($decorator);
	        		$ApplicationDecorator = $Reflection->newInstanceArgs(array($Application));
	        		echo $ApplicationDecorator->display();
        		} else {
        			echo $Application->display();
        		}
        		
        		$log[] = 'call: display';
        	}
        } catch (Exception $e) {
            ob_clean();
            
            if($routing->exists('exceptions') && ($route = $routing->exceptions->find(get_class($e)))) {
                $log[] = 'throw: ' . get_class($e);
                $log[] = 'init: ' . $route->invoke->value;
                
                if(!class_exists($route->invoke->value)) {
                    header('Not found', null, 404);
                    echo "<html><head><title>404 Not found</title></head><body><h1>404 Not found</h1></body></html>";
                    exit;
                }
                
                $Reflection = new ReflectionClass($route->invoke->value);
                $Application = $Reflection->newInstance();
                
                /* @var $Application ApplicationController */
                if(!$Application instanceof ApplicationController) {
                    throw new Exception('ApplicationRunner: Called application is not extends of ApplicationController class.');
                }
            
                $Application->prepare();
                
                if($Application instanceof IViewable) {
                    if($route->exists('decorator')) {
                        $decorator = $route->decorator->value;
                    } else if($routing->defaults->exists('decorator')) {
                        $decorator = $routing->defaults->decorator->value;
                    }
    
                    if($decorator) {
                        $log[] = 'decorate by: ' . $decorator;
                        $Reflection = new ReflectionClass($decorator);
                        $ApplicationDecorator = $Reflection->newInstanceArgs(array($Application));
                        echo $ApplicationDecorator->display();
                    } else {
                        echo $Application->display();
                    }
                    
                    $log[] = 'call: display';
                } else {
                    throw $e;
                }
            } else {
        	   throw $e;
            }
        }
        
        // backtrace route
        // echo "<div style='clear: both;'><br /><br /><br /></div><pre> # " . implode("\n # ", $log) . "</pre>";
    }
    
    private function configure()
    {
        Request::normalize();
        
        $tmp = explode('?', $_SERVER['REQUEST_URI']);
        $path = reset($tmp);
    	$request = trim(str_replace('index.php', '', $path), '/');
    	if(!empty($request)) {
    		$this->args = explode('/', $request);
    	}
    }
}

/**
 * Tab: 4 spaces
 * Encoding: utf-8
 */
?>
